/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"vm/HelperThreads.h"#include"mozilla/DebugOnly.h"#include"mozilla/Maybe.h"#include"mozilla/Unused.h"#include"jsnativestack.h"#include"builtin/Promise.h"#include"frontend/BytecodeCompiler.h"#include"gc/GCInternals.h"#include"jit/IonBuilder.h"#include"threading/CpuCount.h"#include"vm/Debugger.h"#include"vm/ErrorReporting.h"#include"vm/SharedImmutableStringsCache.h"#include"vm/Time.h"#include"vm/TraceLogging.h"#include"vm/Xdr.h"#include"jscntxtinlines.h"#include"jscompartmentinlines.h"#include"jsobjinlines.h"#include"jsscriptinlines.h"#include"vm/NativeObject-inl.h"usingnamespacejs;usingmozilla::ArrayLength;usingmozilla::DebugOnly;usingmozilla::Unused;usingmozilla::TimeDuration;namespacejs{GlobalHelperThreadState*gHelperThreadState=nullptr;}// namespace jsbooljs::CreateHelperThreadsState(){MOZ_ASSERT(!gHelperThreadState);gHelperThreadState=js_new<GlobalHelperThreadState>();returngHelperThreadState!=nullptr;}voidjs::DestroyHelperThreadsState(){MOZ_ASSERT(gHelperThreadState);gHelperThreadState->finish();js_delete(gHelperThreadState);gHelperThreadState=nullptr;}booljs::EnsureHelperThreadsInitialized(){MOZ_ASSERT(gHelperThreadState);returngHelperThreadState->ensureInitialized();}staticsize_tThreadCountForCPUCount(size_tcpuCount){// Create additional threads on top of the number of cores available, to// provide some excess capacity in case threads pause each other.staticconstuint32_tEXCESS_THREADS=4;returncpuCount+EXCESS_THREADS;}voidjs::SetFakeCPUCount(size_tcount){// This must be called before the threads have been initialized.MOZ_ASSERT(!HelperThreadState().threads);HelperThreadState().cpuCount=count;HelperThreadState().threadCount=ThreadCountForCPUCount(count);}booljs::StartOffThreadWasmCompile(wasm::CompileTask*task){AutoLockHelperThreadStatelock;if(!HelperThreadState().wasmWorklist(lock).append(task))returnfalse;HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);returntrue;}booljs::StartOffThreadIonCompile(JSContext*cx,jit::IonBuilder*builder){AutoLockHelperThreadStatelock;if(!HelperThreadState().ionWorklist(lock).append(builder))returnfalse;HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);returntrue;}booljs::StartOffThreadIonFree(jit::IonBuilder*builder,constAutoLockHelperThreadState&lock){MOZ_ASSERT(CanUseExtraThreads());if(!HelperThreadState().ionFreeList(lock).append(builder))returnfalse;HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);returntrue;}/* * Move an IonBuilder for which compilation has either finished, failed, or * been cancelled into the global finished compilation list. All off thread * compilations which are started must eventually be finished. */staticvoidFinishOffThreadIonCompile(jit::IonBuilder*builder,constAutoLockHelperThreadState&lock){AutoEnterOOMUnsafeRegionoomUnsafe;if(!HelperThreadState().ionFinishedList(lock).append(builder))oomUnsafe.crash("FinishOffThreadIonCompile");builder->script()->zoneFromAnyThread()->group()->numFinishedBuilders++;}staticJSRuntime*GetSelectorRuntime(constCompilationSelector&selector){structMatcher{JSRuntime*match(JSScript*script){returnscript->runtimeFromActiveCooperatingThread();}JSRuntime*match(JSCompartment*comp){returncomp->runtimeFromActiveCooperatingThread();}JSRuntime*match(ZonesInStatezbs){returnzbs.runtime;}JSRuntime*match(JSRuntime*runtime){returnruntime;}JSRuntime*match(AllCompilationsall){returnnullptr;}};returnselector.match(Matcher());}staticboolJitDataStructuresExist(constCompilationSelector&selector){structMatcher{boolmatch(JSScript*script){return!!script->compartment()->jitCompartment();}boolmatch(JSCompartment*comp){return!!comp->jitCompartment();}boolmatch(ZonesInStatezbs){returnzbs.runtime->hasJitRuntime();}boolmatch(JSRuntime*runtime){returnruntime->hasJitRuntime();}boolmatch(AllCompilationsall){returntrue;}};returnselector.match(Matcher());}staticboolCompiledScriptMatches(constCompilationSelector&selector,JSScript*target){structScriptMatches{JSScript*target_;boolmatch(JSScript*script){returnscript==target_;}boolmatch(JSCompartment*comp){returncomp==target_->compartment();}boolmatch(JSRuntime*runtime){returnruntime==target_->runtimeFromAnyThread();}boolmatch(AllCompilationsall){returntrue;}boolmatch(ZonesInStatezbs){returnzbs.runtime==target_->runtimeFromAnyThread()&&zbs.state==target_->zoneFromAnyThread()->gcState();}};returnselector.match(ScriptMatches{target});}voidjs::CancelOffThreadIonCompile(constCompilationSelector&selector,booldiscardLazyLinkList){if(!JitDataStructuresExist(selector))return;AutoLockHelperThreadStatelock;if(!HelperThreadState().threads)return;/* Cancel any pending entries for which processing hasn't started. */GlobalHelperThreadState::IonBuilderVector&worklist=HelperThreadState().ionWorklist(lock);for(size_ti=0;i<worklist.length();i++){jit::IonBuilder*builder=worklist[i];if(CompiledScriptMatches(selector,builder->script())){FinishOffThreadIonCompile(builder,lock);HelperThreadState().remove(worklist,&i);}}/* Wait for in progress entries to finish up. */boolcancelled;do{cancelled=false;boolunpaused=false;for(auto&helper:*HelperThreadState().threads){if(helper.ionBuilder()&&CompiledScriptMatches(selector,helper.ionBuilder()->script())){helper.ionBuilder()->cancel();if(helper.pause){helper.pause=false;unpaused=true;}cancelled=true;}}if(unpaused)HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE,lock);if(cancelled)HelperThreadState().wait(lock,GlobalHelperThreadState::CONSUMER);}while(cancelled);/* Cancel code generation for any completed entries. */GlobalHelperThreadState::IonBuilderVector&finished=HelperThreadState().ionFinishedList(lock);for(size_ti=0;i<finished.length();i++){jit::IonBuilder*builder=finished[i];if(CompiledScriptMatches(selector,builder->script())){builder->script()->zone()->group()->numFinishedBuilders--;jit::FinishOffThreadBuilder(nullptr,builder,lock);HelperThreadState().remove(finished,&i);}}/* Cancel lazy linking for pending builders (attached to the ionScript). */if(discardLazyLinkList){MOZ_ASSERT(!selector.is<AllCompilations>());JSRuntime*runtime=GetSelectorRuntime(selector);for(ZoneGroupsItergroup(runtime);!group.done();group.next()){jit::IonBuilder*builder=group->ionLazyLinkList().getFirst();while(builder){jit::IonBuilder*next=builder->getNext();if(CompiledScriptMatches(selector,builder->script()))jit::FinishOffThreadBuilder(runtime,builder,lock);builder=next;}}}}#ifdef DEBUGbooljs::HasOffThreadIonCompile(JSCompartment*comp){AutoLockHelperThreadStatelock;if(!HelperThreadState().threads||comp->isAtomsCompartment())returnfalse;GlobalHelperThreadState::IonBuilderVector&worklist=HelperThreadState().ionWorklist(lock);for(size_ti=0;i<worklist.length();i++){jit::IonBuilder*builder=worklist[i];if(builder->script()->compartment()==comp)returntrue;}for(auto&helper:*HelperThreadState().threads){if(helper.ionBuilder()&&helper.ionBuilder()->script()->compartment()==comp)returntrue;}GlobalHelperThreadState::IonBuilderVector&finished=HelperThreadState().ionFinishedList(lock);for(size_ti=0;i<finished.length();i++){jit::IonBuilder*builder=finished[i];if(builder->script()->compartment()==comp)returntrue;}jit::IonBuilder*builder=comp->zone()->group()->ionLazyLinkList().getFirst();while(builder){if(builder->script()->compartment()==comp)returntrue;builder=builder->getNext();}returnfalse;}#endifstaticconstJSClassOpsparseTaskGlobalClassOps={nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,JS_GlobalObjectTraceHook};staticconstJSClassparseTaskGlobalClass={"internal-parse-task-global",JSCLASS_GLOBAL_FLAGS,&parseTaskGlobalClassOps};ParseTask::ParseTask(ParseTaskKindkind,JSContext*cx,JSObject*parseGlobal,constchar16_t*chars,size_tlength,JS::OffThreadCompileCallbackcallback,void*callbackData):kind(kind),options(cx),data(AsVariant(TwoByteChars(chars,length))),alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),parseGlobal(parseGlobal),callback(callback),callbackData(callbackData),scripts(cx),sourceObjects(cx),overRecursed(false),outOfMemory(false){MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));}ParseTask::ParseTask(ParseTaskKindkind,JSContext*cx,JSObject*parseGlobal,constJS::TranscodeRange&range,JS::OffThreadCompileCallbackcallback,void*callbackData):kind(kind),options(cx),data(AsVariant(range)),alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),parseGlobal(parseGlobal),callback(callback),callbackData(callbackData),scripts(cx),sourceObjects(cx),overRecursed(false),outOfMemory(false){MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));}ParseTask::ParseTask(ParseTaskKindkind,JSContext*cx,JSObject*parseGlobal,JS::TranscodeSources&sources,JS::OffThreadCompileCallbackcallback,void*callbackData):kind(kind),options(cx),data(AsVariant(&sources)),alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),parseGlobal(parseGlobal),callback(callback),callbackData(callbackData),scripts(cx),sourceObjects(cx),overRecursed(false),outOfMemory(false){MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));}boolParseTask::init(JSContext*cx,constReadOnlyCompileOptions&options){if(!this->options.copy(cx,options))returnfalse;returntrue;}voidParseTask::activate(JSRuntime*rt){rt->setUsedByHelperThread(parseGlobal->zone());}boolParseTask::finish(JSContext*cx){for(auto&sourceObject:sourceObjects){RootedScriptSourcesso(cx,sourceObject);if(!ScriptSourceObject::initFromOptions(cx,sso,options))returnfalse;if(!sso->source()->tryCompressOffThread(cx))returnfalse;}returntrue;}ParseTask::~ParseTask(){for(size_ti=0;i<errors.length();i++)js_delete(errors[i]);}voidParseTask::trace(JSTracer*trc){if(parseGlobal->runtimeFromAnyThread()!=trc->runtime())return;Zone*zone=MaybeForwarded(parseGlobal)->zoneFromAnyThread();if(zone->usedByHelperThread()){MOZ_ASSERT(!zone->isCollecting());return;}TraceManuallyBarrieredEdge(trc,&parseGlobal,"ParseTask::parseGlobal");scripts.trace(trc);sourceObjects.trace(trc);}ScriptParseTask::ScriptParseTask(JSContext*cx,JSObject*parseGlobal,constchar16_t*chars,size_tlength,JS::OffThreadCompileCallbackcallback,void*callbackData):ParseTask(ParseTaskKind::Script,cx,parseGlobal,chars,length,callback,callbackData){}voidScriptParseTask::parse(JSContext*cx){auto&range=data.as<TwoByteChars>();SourceBufferHoldersrcBuf(range.begin().get(),range.length(),SourceBufferHolder::NoOwnership);Rooted<ScriptSourceObject*>sourceObject(cx);JSScript*script=frontend::CompileGlobalScript(cx,alloc,ScopeKind::Global,options,srcBuf,/* sourceObjectOut = */&sourceObject.get());if(script)scripts.infallibleAppend(script);if(sourceObject)sourceObjects.infallibleAppend(sourceObject);}ModuleParseTask::ModuleParseTask(JSContext*cx,JSObject*parseGlobal,constchar16_t*chars,size_tlength,JS::OffThreadCompileCallbackcallback,void*callbackData):ParseTask(ParseTaskKind::Module,cx,parseGlobal,chars,length,callback,callbackData){}voidModuleParseTask::parse(JSContext*cx){auto&range=data.as<TwoByteChars>();SourceBufferHoldersrcBuf(range.begin().get(),range.length(),SourceBufferHolder::NoOwnership);Rooted<ScriptSourceObject*>sourceObject(cx);ModuleObject*module=frontend::CompileModule(cx,options,srcBuf,alloc,&sourceObject.get());if(module){scripts.infallibleAppend(module->script());if(sourceObject)sourceObjects.infallibleAppend(sourceObject);}}ScriptDecodeTask::ScriptDecodeTask(JSContext*cx,JSObject*parseGlobal,constJS::TranscodeRange&range,JS::OffThreadCompileCallbackcallback,void*callbackData):ParseTask(ParseTaskKind::ScriptDecode,cx,parseGlobal,range,callback,callbackData){}voidScriptDecodeTask::parse(JSContext*cx){RootedScriptresultScript(cx);Rooted<ScriptSourceObject*>sourceObject(cx);XDROffThreadDecoderdecoder(cx,alloc,&options,/* sourceObjectOut = */&sourceObject.get(),data.as<constJS::TranscodeRange>());decoder.codeScript(&resultScript);MOZ_ASSERT(bool(resultScript)==(decoder.resultCode()==JS::TranscodeResult_Ok));if(decoder.resultCode()==JS::TranscodeResult_Ok){scripts.infallibleAppend(resultScript);if(sourceObject)sourceObjects.infallibleAppend(sourceObject);}}MultiScriptsDecodeTask::MultiScriptsDecodeTask(JSContext*cx,JSObject*parseGlobal,JS::TranscodeSources&sources,JS::OffThreadCompileCallbackcallback,void*callbackData):ParseTask(ParseTaskKind::MultiScriptsDecode,cx,parseGlobal,sources,callback,callbackData){}voidMultiScriptsDecodeTask::parse(JSContext*cx){autosources=data.as<JS::TranscodeSources*>();if(!scripts.reserve(sources->length())||!sourceObjects.reserve(sources->length())){return;}for(auto&source:*sources){CompileOptionsopts(cx,options);opts.setFileAndLine(source.filename,source.lineno);RootedScriptresultScript(cx);Rooted<ScriptSourceObject*>sourceObject(cx);XDROffThreadDecoderdecoder(cx,alloc,&opts,&sourceObject.get(),source.range);decoder.codeScript(&resultScript);MOZ_ASSERT(bool(resultScript)==(decoder.resultCode()==JS::TranscodeResult_Ok));if(decoder.resultCode()!=JS::TranscodeResult_Ok)break;MOZ_ASSERT(resultScript);scripts.infallibleAppend(resultScript);sourceObjects.infallibleAppend(sourceObject);}}voidjs::CancelOffThreadParses(JSRuntime*rt){AutoLockHelperThreadStatelock;if(!HelperThreadState().threads)return;#ifdef DEBUGGlobalHelperThreadState::ParseTaskVector&waitingOnGC=HelperThreadState().parseWaitingOnGC(lock);for(size_ti=0;i<waitingOnGC.length();i++)MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));#endif// Instead of forcibly canceling pending parse tasks, just wait for all scheduled// and in progress ones to complete. Otherwise the final GC may not collect// everything due to zones being used off thread.while(true){boolpending=false;GlobalHelperThreadState::ParseTaskVector&worklist=HelperThreadState().parseWorklist(lock);for(size_ti=0;i<worklist.length();i++){ParseTask*task=worklist[i];if(task->runtimeMatches(rt))pending=true;}if(!pending){boolinProgress=false;for(auto&thread:*HelperThreadState().threads){ParseTask*task=thread.parseTask();if(task&&task->runtimeMatches(rt))inProgress=true;}if(!inProgress)break;}HelperThreadState().wait(lock,GlobalHelperThreadState::CONSUMER);}// Clean up any parse tasks which haven't been finished by the active thread.GlobalHelperThreadState::ParseTaskVector&finished=HelperThreadState().parseFinishedList(lock);while(true){boolfound=false;for(size_ti=0;i<finished.length();i++){ParseTask*task=finished[i];if(task->runtimeMatches(rt)){found=true;AutoUnlockHelperThreadStateunlock(lock);HelperThreadState().cancelParseTask(rt,task->kind,task);}}if(!found)break;}}booljs::OffThreadParsingMustWaitForGC(JSRuntime*rt){// Off thread parsing can't occur during incremental collections on the// atoms compartment, to avoid triggering barriers. (Outside the atoms// compartment, the compilation will use a new zone that is never// collected.) If an atoms-zone GC is in progress, hold off on executing the// parse task until the atoms-zone GC completes (see// EnqueuePendingParseTasksAfterGC).returnrt->activeGCInAtomsZone();}staticboolEnsureConstructor(JSContext*cx,Handle<GlobalObject*>global,JSProtoKeykey){if(!GlobalObject::ensureConstructor(cx,global,key))returnfalse;MOZ_ASSERT(global->getPrototype(key).toObject().isDelegate(),"standard class prototype wasn't a delegate from birth");returntrue;}// Initialize all classes potentially created during parsing for use in parser// data structures, template objects, &c.staticboolEnsureParserCreatedClasses(JSContext*cx,ParseTaskKindkind){Handle<GlobalObject*>global=cx->global();if(!EnsureConstructor(cx,global,JSProto_Function))returnfalse;// needed by functions, also adds object literals' protoif(!EnsureConstructor(cx,global,JSProto_Array))returnfalse;// needed by array literalsif(!EnsureConstructor(cx,global,JSProto_RegExp))returnfalse;// needed by regular expression literalsif(!EnsureConstructor(cx,global,JSProto_Iterator))returnfalse;// needed by ???if(!GlobalObject::initStarGenerators(cx,global))returnfalse;// needed by function*() {} and generator comprehensionsif(kind==ParseTaskKind::Module&&!GlobalObject::ensureModulePrototypesCreated(cx,global))returnfalse;returntrue;}staticJSObject*CreateGlobalForOffThreadParse(JSContext*cx,ParseTaskKindkind,constgc::AutoSuppressGC&nogc){JSCompartment*currentCompartment=cx->compartment();JS::CompartmentOptionscompartmentOptions(currentCompartment->creationOptions(),currentCompartment->behaviors());auto&creationOptions=compartmentOptions.creationOptions();creationOptions.setInvisibleToDebugger(true).setMergeable(true).setNewZoneInNewZoneGroup();// Don't falsely inherit the host's global trace hook.creationOptions.setTrace(nullptr);JSObject*global=JS_NewGlobalObject(cx,&parseTaskGlobalClass,nullptr,JS::FireOnNewGlobalHook,compartmentOptions);if(!global)returnnullptr;JS_SetCompartmentPrincipals(global->compartment(),currentCompartment->principals());// Initialize all classes required for parsing while still on the active// thread, for both the target and the new global so that prototype// pointers can be changed infallibly after parsing finishes.if(!EnsureParserCreatedClasses(cx,kind))returnnullptr;{AutoCompartmentac(cx,global);if(!EnsureParserCreatedClasses(cx,kind))returnnullptr;}returnglobal;}staticboolQueueOffThreadParseTask(JSContext*cx,ParseTask*task){if(OffThreadParsingMustWaitForGC(cx->runtime())){AutoLockHelperThreadStatelock;if(!HelperThreadState().parseWaitingOnGC(lock).append(task)){ReportOutOfMemory(cx);returnfalse;}}else{AutoLockHelperThreadStatelock;if(!HelperThreadState().parseWorklist(lock).append(task)){ReportOutOfMemory(cx);returnfalse;}task->activate(cx->runtime());HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);}returntrue;}template<typenameTaskFunctor>boolStartOffThreadParseTask(JSContext*cx,constReadOnlyCompileOptions&options,ParseTaskKindkind,TaskFunctor&taskFunctor){// Suppress GC so that calls below do not trigger a new incremental GC// which could require barriers on the atoms compartment.gc::AutoSuppressGCnogc(cx);gc::AutoAssertNoNurseryAllocnoNurseryAlloc;AutoSuppressAllocationMetadataBuildersuppressMetadata(cx);JSObject*global=CreateGlobalForOffThreadParse(cx,kind,nogc);if(!global)returnfalse;ScopedJSDeletePtr<ParseTask>task(taskFunctor(global));if(!task)returnfalse;if(!task->init(cx,options)||!QueueOffThreadParseTask(cx,task))returnfalse;task.forget();returntrue;}booljs::StartOffThreadParseScript(JSContext*cx,constReadOnlyCompileOptions&options,constchar16_t*chars,size_tlength,JS::OffThreadCompileCallbackcallback,void*callbackData){autofunctor=[&](JSObject*global)->ScriptParseTask*{returncx->new_<ScriptParseTask>(cx,global,chars,length,callback,callbackData);};returnStartOffThreadParseTask(cx,options,ParseTaskKind::Script,functor);}booljs::StartOffThreadParseModule(JSContext*cx,constReadOnlyCompileOptions&options,constchar16_t*chars,size_tlength,JS::OffThreadCompileCallbackcallback,void*callbackData){autofunctor=[&](JSObject*global)->ModuleParseTask*{returncx->new_<ModuleParseTask>(cx,global,chars,length,callback,callbackData);};returnStartOffThreadParseTask(cx,options,ParseTaskKind::Module,functor);}booljs::StartOffThreadDecodeScript(JSContext*cx,constReadOnlyCompileOptions&options,constJS::TranscodeRange&range,JS::OffThreadCompileCallbackcallback,void*callbackData){autofunctor=[&](JSObject*global)->ScriptDecodeTask*{returncx->new_<ScriptDecodeTask>(cx,global,range,callback,callbackData);};returnStartOffThreadParseTask(cx,options,ParseTaskKind::ScriptDecode,functor);}booljs::StartOffThreadDecodeMultiScripts(JSContext*cx,constReadOnlyCompileOptions&options,JS::TranscodeSources&sources,JS::OffThreadCompileCallbackcallback,void*callbackData){autofunctor=[&](JSObject*global)->MultiScriptsDecodeTask*{returncx->new_<MultiScriptsDecodeTask>(cx,global,sources,callback,callbackData);};returnStartOffThreadParseTask(cx,options,ParseTaskKind::MultiScriptsDecode,functor);}voidjs::EnqueuePendingParseTasksAfterGC(JSRuntime*rt){MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));GlobalHelperThreadState::ParseTaskVectornewTasks;{AutoLockHelperThreadStatelock;GlobalHelperThreadState::ParseTaskVector&waiting=HelperThreadState().parseWaitingOnGC(lock);for(size_ti=0;i<waiting.length();i++){ParseTask*task=waiting[i];if(task->runtimeMatches(rt)&&!task->parseGlobal->zone()->wasGCStarted()){AutoEnterOOMUnsafeRegionoomUnsafe;if(!newTasks.append(task))oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");HelperThreadState().remove(waiting,&i);}}}if(newTasks.empty())return;// This logic should mirror the contents of the !activeGCInAtomsZone()// branch in StartOffThreadParseScript:for(size_ti=0;i<newTasks.length();i++)newTasks[i]->activate(rt);AutoLockHelperThreadStatelock;{AutoEnterOOMUnsafeRegionoomUnsafe;if(!HelperThreadState().parseWorklist(lock).appendAll(newTasks))oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");}HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER,lock);}staticconstuint32_tkDefaultHelperStackSize=2048*1024;staticconstuint32_tkDefaultHelperStackQuota=1800*1024;// TSan enforces a minimum stack size that's just slightly larger than our// default helper stack size. It does this to store blobs of TSan-specific// data on each thread's stack. Unfortunately, that means that even though// we'll actually receive a larger stack than we requested, the effective// usable space of that stack is significantly less than what we expect.// To offset TSan stealing our stack space from underneath us, double the// default.//// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't// require all the thread-specific state that TSan does.#if defined(MOZ_TSAN)staticconstuint32_tHELPER_STACK_SIZE=2*kDefaultHelperStackSize;staticconstuint32_tHELPER_STACK_QUOTA=2*kDefaultHelperStackQuota;#elsestaticconstuint32_tHELPER_STACK_SIZE=kDefaultHelperStackSize;staticconstuint32_tHELPER_STACK_QUOTA=kDefaultHelperStackQuota;#endifboolGlobalHelperThreadState::ensureInitialized(){MOZ_ASSERT(CanUseExtraThreads());MOZ_ASSERT(this==&HelperThreadState());AutoLockHelperThreadStatelock;if(threads)returntrue;threads=js::MakeUnique<HelperThreadVector>();if(!threads||!threads->initCapacity(threadCount))returnfalse;for(size_ti=0;i<threadCount;i++){threads->infallibleEmplaceBack();HelperThread&helper=(*threads)[i];helper.thread=mozilla::Some(Thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)));if(!helper.thread->init(HelperThread::ThreadMain,&helper))gotoerror;continue;error:// Ensure that we do not leave uninitialized threads in the `threads`// vector.threads->popBack();finishThreads();returnfalse;}returntrue;}GlobalHelperThreadState::GlobalHelperThreadState():cpuCount(0),threadCount(0),threads(nullptr),wasmCompilationInProgress(false),numWasmFailedJobs(0),helperLock(mutexid::GlobalHelperThreadState){cpuCount=GetCPUCount();threadCount=ThreadCountForCPUCount(cpuCount);MOZ_ASSERT(cpuCount>0,"GetCPUCount() seems broken");}voidGlobalHelperThreadState::finish(){finishThreads();// Make sure there are no Ion free tasks left. We check this here because,// unlike the other tasks, we don't explicitly block on this when// destroying a runtime.AutoLockHelperThreadStatelock;auto&freeList=ionFreeList(lock);while(!freeList.empty())jit::FreeIonBuilder(freeList.popCopy());}voidGlobalHelperThreadState::finishThreads(){if(!threads)return;MOZ_ASSERT(CanUseExtraThreads());for(auto&thread:*threads)thread.destroy();threads.reset(nullptr);}voidGlobalHelperThreadState::lock(){helperLock.lock();}voidGlobalHelperThreadState::unlock(){helperLock.unlock();}#ifdef DEBUGboolGlobalHelperThreadState::isLockedByCurrentThread(){returnhelperLock.ownedByCurrentThread();}#endif // DEBUGvoidGlobalHelperThreadState::wait(AutoLockHelperThreadState&locked,CondVarwhich,TimeDurationtimeout/* = TimeDuration::Forever() */){whichWakeup(which).wait_for(locked,timeout);}voidGlobalHelperThreadState::notifyAll(CondVarwhich,constAutoLockHelperThreadState&){whichWakeup(which).notify_all();}voidGlobalHelperThreadState::notifyOne(CondVarwhich,constAutoLockHelperThreadState&){whichWakeup(which).notify_one();}boolGlobalHelperThreadState::hasActiveThreads(constAutoLockHelperThreadState&){if(!threads)returnfalse;for(auto&thread:*threads){if(!thread.idle())returntrue;}returnfalse;}voidGlobalHelperThreadState::waitForAllThreads(){CancelOffThreadIonCompile();AutoLockHelperThreadStatelock;while(hasActiveThreads(lock))wait(lock,CONSUMER);}template<typenameT>boolGlobalHelperThreadState::checkTaskThreadLimit(size_tmaxThreads)const{if(maxThreads>=threadCount)returntrue;size_tcount=0;for(auto&thread:*threads){if(thread.currentTask.isSome()&&thread.currentTask->is<T>())count++;if(count>=maxThreads)returnfalse;}returntrue;}structMOZ_RAIIAutoSetContextRuntime{explicitAutoSetContextRuntime(JSRuntime*rt){TlsContext.get()->setRuntime(rt);}~AutoSetContextRuntime(){TlsContext.get()->setRuntime(nullptr);}};staticinlineboolIsHelperThreadSimulatingOOM(js::oom::ThreadTypethreadType){#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)returnjs::oom::targetThread==threadType;#elsereturnfalse;#endif}size_tGlobalHelperThreadState::maxIonCompilationThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_ION))return1;returnthreadCount;}size_tGlobalHelperThreadState::maxUnpausedIonCompilationThreads()const{return1;}size_tGlobalHelperThreadState::maxWasmCompilationThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_WASM))return1;returncpuCount;}size_tGlobalHelperThreadState::maxParseThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_PARSE))return1;// Don't allow simultaneous off thread parses, to reduce contention on the// atoms table. Note that wasm compilation depends on this to avoid// stalling the helper thread, as off thread parse tasks can trigger and// block on other off thread wasm compilation tasks.return1;}size_tGlobalHelperThreadState::maxCompressionThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_COMPRESS))return1;// Compression is triggered on major GCs to compress ScriptSources. It is// considered low priority work.return1;}size_tGlobalHelperThreadState::maxGCHelperThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCHELPER))return1;returnthreadCount;}size_tGlobalHelperThreadState::maxGCParallelThreads()const{if(IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCPARALLEL))return1;returnthreadCount;}boolGlobalHelperThreadState::canStartWasmCompile(constAutoLockHelperThreadState&lock,boolassumeThreadAvailable){// Don't execute an wasm job if an earlier one failed.if(wasmWorklist(lock).empty()||numWasmFailedJobs)returnfalse;if(assumeThreadAvailable)returntrue;// Honor the maximum allowed threads to compile wasm jobs at once,// to avoid oversaturating the machine.if(!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))returnfalse;returntrue;}boolGlobalHelperThreadState::canStartPromiseTask(constAutoLockHelperThreadState&lock){return!promiseTasks(lock).empty();}staticboolIonBuilderHasHigherPriority(jit::IonBuilder*first,jit::IonBuilder*second){// This method can return whatever it wants, though it really ought to be a// total order. The ordering is allowed to race (change on the fly), however.// A lower optimization level indicates a higher priority.if(first->optimizationInfo().level()!=second->optimizationInfo().level())returnfirst->optimizationInfo().level()<second->optimizationInfo().level();// A script without an IonScript has precedence on one with.if(first->scriptHasIonScript()!=second->scriptHasIonScript())return!first->scriptHasIonScript();// A higher warm-up counter indicates a higher priority.returnfirst->script()->getWarmUpCount()/first->script()->length()>second->script()->getWarmUpCount()/second->script()->length();}boolGlobalHelperThreadState::canStartIonCompile(constAutoLockHelperThreadState&lock){return!ionWorklist(lock).empty()&&checkTaskThreadLimit<jit::IonBuilder*>(maxIonCompilationThreads());}boolGlobalHelperThreadState::canStartIonFreeTask(constAutoLockHelperThreadState&lock){return!ionFreeList(lock).empty();}jit::IonBuilder*GlobalHelperThreadState::highestPriorityPendingIonCompile(constAutoLockHelperThreadState&lock,boolremove/* = false */){auto&worklist=ionWorklist(lock);if(worklist.empty()){MOZ_ASSERT(!remove);returnnullptr;}// Get the highest priority IonBuilder which has not started compilation yet.size_tindex=0;for(size_ti=1;i<worklist.length();i++){if(IonBuilderHasHigherPriority(worklist[i],worklist[index]))index=i;}jit::IonBuilder*builder=worklist[index];if(remove)worklist.erase(&worklist[index]);returnbuilder;}HelperThread*GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold(constAutoLockHelperThreadState&lock){// Get the lowest priority IonBuilder which has started compilation and// isn't paused, unless there are still fewer than the maximum number of// such builders permitted.size_tnumBuilderThreads=0;HelperThread*thread=nullptr;for(auto&thisThread:*threads){if(thisThread.ionBuilder()&&!thisThread.pause){numBuilderThreads++;if(!thread||IonBuilderHasHigherPriority(thread->ionBuilder(),thisThread.ionBuilder())){thread=&thisThread;}}}if(numBuilderThreads<maxUnpausedIonCompilationThreads())returnnullptr;returnthread;}HelperThread*GlobalHelperThreadState::highestPriorityPausedIonCompile(constAutoLockHelperThreadState&lock){// Get the highest priority IonBuilder which has started compilation but// which was subsequently paused.HelperThread*thread=nullptr;for(auto&thisThread:*threads){if(thisThread.pause){// Currently, only threads with IonBuilders can be paused.MOZ_ASSERT(thisThread.ionBuilder());if(!thread||IonBuilderHasHigherPriority(thisThread.ionBuilder(),thread->ionBuilder())){thread=&thisThread;}}}returnthread;}boolGlobalHelperThreadState::pendingIonCompileHasSufficientPriority(constAutoLockHelperThreadState&lock){// Can't compile anything if there are no scripts to compile.if(!canStartIonCompile(lock))returnfalse;// Count the number of threads currently compiling scripts, and look for// the thread with the lowest priority.HelperThread*lowestPriorityThread=lowestPriorityUnpausedIonCompileAtThreshold(lock);// If the number of threads building scripts is less than the maximum, the// compilation can start immediately.if(!lowestPriorityThread)returntrue;// If there is a builder in the worklist with higher priority than some// builder currently being compiled, then that current compilation can be// paused, so allow the compilation.if(IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(lock),lowestPriorityThread->ionBuilder()))returntrue;// Compilation will have to wait until one of the active compilations finishes.returnfalse;}boolGlobalHelperThreadState::canStartParseTask(constAutoLockHelperThreadState&lock){return!parseWorklist(lock).empty()&&checkTaskThreadLimit<ParseTask*>(maxParseThreads());}boolGlobalHelperThreadState::canStartCompressionTask(constAutoLockHelperThreadState&lock){return!compressionWorklist(lock).empty()&&checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());}voidGlobalHelperThreadState::startHandlingCompressionTasks(constAutoLockHelperThreadState&lock){scheduleCompressionTasks(lock);if(canStartCompressionTask(lock))notifyOne(PRODUCER,lock);}voidGlobalHelperThreadState::scheduleCompressionTasks(constAutoLockHelperThreadState&lock){auto&pending=compressionPendingList(lock);auto&worklist=compressionWorklist(lock);for(size_ti=0;i<pending.length();i++){if(pending[i]->shouldStart()){// OOMing during appending results in the task not being scheduled// and deleted.Unused<<worklist.append(Move(pending[i]));remove(pending,&i);}}}boolGlobalHelperThreadState::canStartGCHelperTask(constAutoLockHelperThreadState&lock){return!gcHelperWorklist(lock).empty()&&checkTaskThreadLimit<GCHelperState*>(maxGCHelperThreads());}boolGlobalHelperThreadState::canStartGCParallelTask(constAutoLockHelperThreadState&lock){return!gcParallelWorklist(lock).empty()&&checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());}js::GCParallelTask::~GCParallelTask(){// Only most-derived classes' destructors may do the join: base class// destructors run after those for derived classes' members, so a join in a// base class can't ensure that the task is done using the members. All we// can do now is check that someone has previously stopped the task.#ifdef DEBUGmozilla::Maybe<AutoLockHelperThreadState>helperLock;if(!HelperThreadState().isLockedByCurrentThread())helperLock.emplace();MOZ_ASSERT(state==NotStarted);#endif}booljs::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState&lock){// Tasks cannot be started twice.MOZ_ASSERT(state==NotStarted);// If we do the shutdown GC before running anything, we may never// have initialized the helper threads. Just use the serial path// since we cannot safely intialize them at this point.if(!HelperThreadState().threads)returnfalse;if(!HelperThreadState().gcParallelWorklist(lock).append(this))returnfalse;state=Dispatched;HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);returntrue;}booljs::GCParallelTask::start(){AutoLockHelperThreadStatehelperLock;returnstartWithLockHeld(helperLock);}voidjs::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState&locked){if(state==NotStarted)return;while(state!=Finished)HelperThreadState().wait(locked,GlobalHelperThreadState::CONSUMER);state=NotStarted;cancel_=false;}voidjs::GCParallelTask::join(){AutoLockHelperThreadStatehelperLock;joinWithLockHeld(helperLock);}voidjs::GCParallelTask::runFromActiveCooperatingThread(JSRuntime*rt){MOZ_ASSERT(state==NotStarted);MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt));mozilla::TimeStamptimeStart=mozilla::TimeStamp::Now();run();duration_=mozilla::TimeStamp::Now()-timeStart;}voidjs::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState&locked){AutoSetContextRuntimeascr(runtime());gc::AutoSetThreadIsPerformingGCperformingGC;{AutoUnlockHelperThreadStateparallelSection(locked);mozilla::TimeStamptimeStart=mozilla::TimeStamp::Now();TlsContext.get()->heapState=JS::HeapState::MajorCollecting;run();TlsContext.get()->heapState=JS::HeapState::Idle;duration_=mozilla::TimeStamp::Now()-timeStart;}state=Finished;HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);}booljs::GCParallelTask::isRunningWithLockHeld(constAutoLockHelperThreadState&locked)const{returnstate==Dispatched;}booljs::GCParallelTask::isRunning()const{AutoLockHelperThreadStatehelperLock;returnisRunningWithLockHeld(helperLock);}voidHelperThread::handleGCParallelWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartGCParallelTask(locked));MOZ_ASSERT(idle());TraceLoggerThread*logger=TraceLoggerForCurrentThread();AutoTraceLoglogCompile(logger,TraceLogger_GC);currentTask.emplace(HelperThreadState().gcParallelWorklist(locked).popCopy());gcParallelTask()->runFromHelperThread(locked);currentTask.reset();HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);}staticvoidLeaveParseTaskZone(JSRuntime*rt,ParseTask*task){// Mark the zone as no longer in use by a helper thread, and available// to be collected by the GC.rt->clearUsedByHelperThread(task->parseGlobal->zone());}ParseTask*GlobalHelperThreadState::removeFinishedParseTask(ParseTaskKindkind,void*token){// The token is a ParseTask* which should be in the finished list.// Find and remove its entry.AutoLockHelperThreadStatelock;ParseTaskVector&finished=parseFinishedList(lock);for(size_ti=0;i<finished.length();i++){if(finished[i]==token){ParseTask*parseTask=finished[i];remove(finished,&i);MOZ_ASSERT(parseTask);MOZ_ASSERT(parseTask->kind==kind);returnparseTask;}}MOZ_CRASH("Invalid ParseTask token");}template<typenameF,typename>boolGlobalHelperThreadState::finishParseTask(JSContext*cx,ParseTaskKindkind,void*token,F&&finishCallback){MOZ_ASSERT(cx->compartment());ScopedJSDeletePtr<ParseTask>parseTask(removeFinishedParseTask(kind,token));// Make sure we have all the constructors we need for the prototype// remapping below, since we can't GC while that's happening.Rooted<GlobalObject*>global(cx,&cx->global()->as<GlobalObject>());if(!EnsureParserCreatedClasses(cx,kind)){LeaveParseTaskZone(cx->runtime(),parseTask);returnfalse;}mergeParseTaskCompartment(cx,parseTask,global,cx->compartment());boolok=finishCallback(parseTask);for(auto&script:parseTask->scripts)releaseAssertSameCompartment(cx,script);if(!parseTask->finish(cx)||!ok)returnfalse;// Report out of memory errors eagerly, or errors could be malformed.if(parseTask->outOfMemory){ReportOutOfMemory(cx);returnfalse;}// Report any error or warnings generated during the parse, and inform the// debugger about the compiled scripts.for(size_ti=0;i<parseTask->errors.length();i++)parseTask->errors[i]->throwError(cx);if(parseTask->overRecursed)ReportOverRecursed(cx);if(cx->isExceptionPending())returnfalse;returntrue;}JSScript*GlobalHelperThreadState::finishParseTask(JSContext*cx,ParseTaskKindkind,void*token){JS::RootedScriptscript(cx);boolok=finishParseTask(cx,kind,token,[&script](ParseTask*parseTask){MOZ_RELEASE_ASSERT(parseTask->scripts.length()<=1);if(parseTask->scripts.length()>0)script=parseTask->scripts[0];returntrue;});if(!ok)returnnullptr;if(!script){// No error was reported, but no script produced. Assume we hit out of// memory.ReportOutOfMemory(cx);returnnullptr;}// The Debugger only needs to be told about the topmost script that was compiled.Debugger::onNewScript(cx,script);returnscript;}boolGlobalHelperThreadState::finishParseTask(JSContext*cx,ParseTaskKindkind,void*token,MutableHandle<ScriptVector>scripts){size_texpectedLength=0;boolok=finishParseTask(cx,kind,token,[&scripts,&expectedLength](ParseTask*parseTask){expectedLength=parseTask->data.as<JS::TranscodeSources*>()->length();if(!scripts.reserve(parseTask->scripts.length()))returnfalse;for(auto&script:parseTask->scripts)scripts.infallibleAppend(script);returntrue;});if(!ok)returnfalse;if(scripts.length()!=expectedLength){// No error was reported, but fewer scripts produced than expected.// Assume we hit out of memory.ReportOutOfMemory(cx);returnfalse;}// The Debugger only needs to be told about the topmost script that was compiled.JS::RootedScriptrooted(cx);for(auto&script:scripts){MOZ_ASSERT(script->isGlobalCode());rooted=script;Debugger::onNewScript(cx,rooted);}returntrue;}JSScript*GlobalHelperThreadState::finishScriptParseTask(JSContext*cx,void*token){JSScript*script=finishParseTask(cx,ParseTaskKind::Script,token);MOZ_ASSERT_IF(script,script->isGlobalCode());returnscript;}JSScript*GlobalHelperThreadState::finishScriptDecodeTask(JSContext*cx,void*token){JSScript*script=finishParseTask(cx,ParseTaskKind::ScriptDecode,token);MOZ_ASSERT_IF(script,script->isGlobalCode());returnscript;}boolGlobalHelperThreadState::finishMultiScriptsDecodeTask(JSContext*cx,void*token,MutableHandle<ScriptVector>scripts){returnfinishParseTask(cx,ParseTaskKind::MultiScriptsDecode,token,scripts);}JSObject*GlobalHelperThreadState::finishModuleParseTask(JSContext*cx,void*token){JSScript*script=finishParseTask(cx,ParseTaskKind::Module,token);if(!script)returnnullptr;MOZ_ASSERT(script->module());RootedModuleObjectmodule(cx,script->module());module->fixEnvironmentsAfterCompartmentMerge();if(!ModuleObject::Freeze(cx,module))returnnullptr;returnmodule;}voidGlobalHelperThreadState::cancelParseTask(JSRuntime*rt,ParseTaskKindkind,void*token){ScopedJSDeletePtr<ParseTask>parseTask(removeFinishedParseTask(kind,token));LeaveParseTaskZone(rt,parseTask);}JSObject*GlobalObject::getStarGeneratorFunctionPrototype(){constValue&v=getReservedSlot(STAR_GENERATOR_FUNCTION_PROTO);returnv.isObject()?&v.toObject():nullptr;}voidGlobalHelperThreadState::mergeParseTaskCompartment(JSContext*cx,ParseTask*parseTask,Handle<GlobalObject*>global,JSCompartment*dest){// Finish any ongoing incremental GC that may affect the destination zone.if(JS::IsIncrementalGCInProgress(cx)&&dest->zone()->wasGCStarted())JS::FinishIncrementalGC(cx,JS::gcreason::API);// After we call LeaveParseTaskZone() it's not safe to GC until we have// finished merging the contents of the parse task's compartment into the// destination compartment.JS::AutoAssertNoGCnogc(cx);LeaveParseTaskZone(cx->runtime(),parseTask);AutoCompartmentac(cx,parseTask->parseGlobal);{// Generator functions don't have Function.prototype as prototype but a// different function object, so the IdentifyStandardPrototype trick// below won't work. Just special-case it.GlobalObject*parseGlobal=&parseTask->parseGlobal->as<GlobalObject>();JSObject*parseTaskStarGenFunctionProto=parseGlobal->getStarGeneratorFunctionPrototype();// Module objects don't have standard prototypes either.JSObject*moduleProto=parseGlobal->maybeGetModulePrototype();JSObject*importEntryProto=parseGlobal->maybeGetImportEntryPrototype();JSObject*exportEntryProto=parseGlobal->maybeGetExportEntryPrototype();// Point the prototypes of any objects in the script's compartment to refer// to the corresponding prototype in the new compartment. This will briefly// create cross compartment pointers, which will be fixed by the// MergeCompartments call below.Zone*parseZone=parseTask->parseGlobal->zone();for(autogroup=parseZone->cellIter<ObjectGroup>();!group.done();group.next()){TaggedProtoproto(group->proto());if(!proto.isObject())continue;JSObject*protoObj=proto.toObject();JSObject*newProto;JSProtoKeykey=JS::IdentifyStandardPrototype(protoObj);if(key!=JSProto_Null){MOZ_ASSERT(key==JSProto_Object||key==JSProto_Array||key==JSProto_Function||key==JSProto_RegExp||key==JSProto_Iterator);newProto=GetBuiltinPrototypePure(global,key);}elseif(protoObj==parseTaskStarGenFunctionProto){newProto=global->getStarGeneratorFunctionPrototype();}elseif(protoObj==moduleProto){newProto=global->getModulePrototype();}elseif(protoObj==importEntryProto){newProto=global->getImportEntryPrototype();}elseif(protoObj==exportEntryProto){newProto=global->getExportEntryPrototype();}else{continue;}group->setProtoUnchecked(TaggedProto(newProto));}}// Move the parsed script and all its contents into the desired compartment.gc::MergeCompartments(parseTask->parseGlobal->compartment(),dest);}voidHelperThread::destroy(){if(thread.isSome()){{AutoLockHelperThreadStatelock;terminate=true;/* Notify all helpers, to ensure that this thread wakes up. */HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER,lock);}thread->join();thread.reset();}}/* static */voidHelperThread::ThreadMain(void*arg){ThisThread::SetName("JS Helper");static_cast<HelperThread*>(arg)->threadLoop();Mutex::ShutDown();}voidHelperThread::handleWasmWorkload(AutoLockHelperThreadState&locked,boolassumeThreadAvailable){MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked,assumeThreadAvailable));MOZ_ASSERT(idle());currentTask.emplace(HelperThreadState().wasmWorklist(locked).popCopy());boolsuccess=false;UniqueCharserror;wasm::CompileTask*task=wasmTask();{AutoUnlockHelperThreadStateunlock(locked);success=wasm::CompileFunction(task,&error);}// On success, try to move work to the finished list.if(success)success=HelperThreadState().wasmFinishedList(locked).append(task);// On failure, note the failure for harvesting by the parent.if(!success){HelperThreadState().noteWasmFailure(locked);HelperThreadState().setWasmError(locked,Move(error));}// Notify the active thread in case it's waiting.HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);currentTask.reset();}boolHelperThread::handleWasmIdleWorkload(AutoLockHelperThreadState&locked){// Perform wasm compilation work on a HelperThread that is running// ModuleGenerator instead of blocking while other compilation threads// finish. This removes a source of deadlocks, as putting all threads to// work guarantees forward progress for compilation.// The current thread has already been accounted for, so don't guard on// thread subscription when checking whether we can do work.if(HelperThreadState().canStartWasmCompile(locked,/*assumeThreadAvailable=*/true)){HelperTaskUnionoldTask=currentTask.value();currentTask.reset();js::oom::ThreadTypeoldType=(js::oom::ThreadType)js::oom::GetThreadType();js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM);handleWasmWorkload(locked,/*assumeThreadAvailable=*/true);js::oom::SetThreadType(oldType);currentTask.emplace(oldTask);returntrue;}returnfalse;}voidHelperThread::handlePromiseTaskWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartPromiseTask(locked));MOZ_ASSERT(idle());PromiseTask*task=HelperThreadState().promiseTasks(locked).popCopy();currentTask.emplace(task);{AutoUnlockHelperThreadStateunlock(locked);task->execute();if(!task->runtime()->finishAsyncTaskCallback(task)){// We cannot simply delete the task now because the PromiseTask must// be destroyed on its runtime's thread. Add it to a list of tasks// to delete before the next GC.AutoEnterOOMUnsafeRegionoomUnsafe;if(!task->runtime()->promiseTasksToDestroy.lock()->append(task))oomUnsafe.crash("handlePromiseTaskWorkload");}}// Notify the active thread in case it's waiting.HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);currentTask.reset();}voidHelperThread::handleIonWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartIonCompile(locked));MOZ_ASSERT(idle());// Find the IonBuilder in the worklist with the highest priority, and// remove it from the worklist.jit::IonBuilder*builder=HelperThreadState().highestPriorityPendingIonCompile(locked,/* remove = */true);// If there are now too many threads with active IonBuilders, indicate to// the one with the lowest priority that it should pause. Note that due to// builder priorities changing since pendingIonCompileHasSufficientPriority// was called, the builder we are pausing may actually be higher priority// than the one we are about to start. Oh well.HelperThread*other=HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold(locked);if(other){MOZ_ASSERT(other->ionBuilder()&&!other->pause);other->pause=true;}currentTask.emplace(builder);builder->setPauseFlag(&pause);JSRuntime*rt=builder->script()->compartment()->runtimeFromAnyThread();{AutoUnlockHelperThreadStateunlock(locked);TraceLoggerThread*logger=TraceLoggerForCurrentThread();TraceLoggerEventevent(TraceLogger_AnnotateScripts,builder->script());AutoTraceLoglogScript(logger,event);AutoTraceLoglogCompile(logger,TraceLogger_IonCompilation);AutoSetContextRuntimeascr(rt);jit::JitContextjctx(jit::CompileRuntime::get(rt),jit::CompileCompartment::get(builder->script()->compartment()),&builder->alloc());builder->setBackgroundCodegen(jit::CompileBackEnd(builder));}FinishOffThreadIonCompile(builder,locked);// Ping any thread currently operating on the compiled script's zone group// so that the compiled code can be incorporated at the next interrupt// callback. Don't interrupt Ion code for this, as this incorporation can// be delayed indefinitely without affecting performance as long as the// active thread is actually executing Ion code.//// This must happen before the current task is reset. DestroyContext// cancels in progress Ion compilations before destroying its target// context, and after we reset the current task we are no longer considered// to be Ion compiling.JSContext*target=builder->script()->zoneFromAnyThread()->group()->ownerContext().context();if(target)target->requestInterrupt(JSContext::RequestInterruptCanWait);currentTask.reset();pause=false;// Notify the active thread in case it is waiting for the compilation to finish.HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);// When finishing Ion compilation jobs, we can start unpausing compilation// threads that were paused to restrict the number of active compilations.// Only unpause one at a time, to make sure we don't exceed the restriction.// Since threads are currently only paused for Ion compilations, this// strategy will eventually unpause all paused threads, regardless of how// many there are, since each thread we unpause will eventually finish and// end up back here.if(HelperThread*other=HelperThreadState().highestPriorityPausedIonCompile(locked)){MOZ_ASSERT(other->ionBuilder()&&other->pause);// Only unpause the other thread if there isn't a higher priority// builder which this thread or another can start on.jit::IonBuilder*builder=HelperThreadState().highestPriorityPendingIonCompile(locked);if(!builder||IonBuilderHasHigherPriority(other->ionBuilder(),builder)){other->pause=false;// Notify all paused threads, to make sure the one we just// unpaused wakes up.HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE,locked);}}}voidHelperThread::handleIonFreeWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(idle());MOZ_ASSERT(HelperThreadState().canStartIonFreeTask(locked));auto&freeList=HelperThreadState().ionFreeList(locked);jit::IonBuilder*builder=freeList.popCopy();{AutoUnlockHelperThreadStateunlock(locked);FreeIonBuilder(builder);}}HelperThread*js::CurrentHelperThread(){if(!HelperThreadState().threads)returnnullptr;autothreadId=ThisThread::GetId();for(auto&thisThread:*HelperThreadState().threads){if(thisThread.thread.isSome()&&threadId==thisThread.thread->get_id())return&thisThread;}returnnullptr;}voidjs::PauseCurrentHelperThread(){TraceLoggerThread*logger=TraceLoggerForCurrentThread();AutoTraceLoglogPaused(logger,TraceLogger_IonCompilationPaused);HelperThread*thread=CurrentHelperThread();MOZ_ASSERT(thread);AutoLockHelperThreadStatelock;while(thread->pause)HelperThreadState().wait(lock,GlobalHelperThreadState::PAUSE);}boolJSContext::addPendingCompileError(js::CompileError**error){autoerrorPtr=make_unique<js::CompileError>();if(!errorPtr)returnfalse;if(!helperThread()->parseTask()->errors.append(errorPtr.get())){ReportOutOfMemory(this);returnfalse;}*error=errorPtr.release();returntrue;}voidJSContext::addPendingOverRecursed(){if(helperThread()->parseTask())helperThread()->parseTask()->overRecursed=true;}voidJSContext::addPendingOutOfMemory(){// Keep in sync with recoverFromOutOfMemory.if(helperThread()->parseTask())helperThread()->parseTask()->outOfMemory=true;}voidHelperThread::handleParseWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartParseTask(locked));MOZ_ASSERT(idle());currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());ParseTask*task=parseTask();{AutoUnlockHelperThreadStateunlock(locked);AutoSetContextRuntimeascr(task->parseGlobal->runtimeFromAnyThread());JSContext*cx=TlsContext.get();AutoCompartmentac(cx,task->parseGlobal);task->parse(cx);}// The callback is invoked while we are still off thread.task->callback(task,task->callbackData);// FinishOffThreadScript will need to be called on the script to// migrate it into the correct compartment.{AutoEnterOOMUnsafeRegionoomUnsafe;if(!HelperThreadState().parseFinishedList(locked).append(task))oomUnsafe.crash("handleParseWorkload");}currentTask.reset();// Notify the active thread in case it is waiting for the parse/emit to finish.HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);}voidHelperThread::handleCompressionWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));MOZ_ASSERT(idle());UniquePtr<SourceCompressionTask>task;{auto&worklist=HelperThreadState().compressionWorklist(locked);task=Move(worklist.back());worklist.popBack();currentTask.emplace(task.get());}{AutoUnlockHelperThreadStateunlock(locked);TraceLoggerThread*logger=TraceLoggerForCurrentThread();AutoTraceLoglogCompile(logger,TraceLogger_CompressSource);task->work();}{AutoEnterOOMUnsafeRegionoomUnsafe;if(!HelperThreadState().compressionFinishedList(locked).append(Move(task)))oomUnsafe.crash("handleCompressionWorkload");}currentTask.reset();// Notify the active thread in case it is waiting for the compression to finish.HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);}booljs::EnqueueOffThreadCompression(JSContext*cx,UniquePtr<SourceCompressionTask>task){AutoLockHelperThreadStatelock;auto&pending=HelperThreadState().compressionPendingList(lock);if(!pending.append(Move(task))){if(!cx->helperThread())ReportOutOfMemory(cx);returnfalse;}returntrue;}template<typenameT>staticvoidClearCompressionTaskList(T&list,JSRuntime*runtime){for(size_ti=0;i<list.length();i++){if(list[i]->runtimeMatches(runtime))HelperThreadState().remove(list,&i);}}voidjs::CancelOffThreadCompressions(JSRuntime*runtime){AutoLockHelperThreadStatelock;if(!HelperThreadState().threads)return;// Cancel all pending compression tasks.ClearCompressionTaskList(HelperThreadState().compressionPendingList(lock),runtime);ClearCompressionTaskList(HelperThreadState().compressionWorklist(lock),runtime);// Cancel all in-process compression tasks and wait for them to join so we// clean up the finished tasks.while(true){boolinProgress=false;for(auto&thread:*HelperThreadState().threads){SourceCompressionTask*task=thread.compressionTask();if(task&&task->runtimeMatches(runtime))inProgress=true;}if(!inProgress)break;HelperThreadState().wait(lock,GlobalHelperThreadState::CONSUMER);}// Clean up finished tasks.ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock),runtime);}booljs::StartPromiseTask(JSContext*cx,UniquePtr<PromiseTask>task){// Execute synchronously if there are no helper threads.if(!CanUseExtraThreads())returntask->executeAndFinish(cx);// If we fail to start, by interface contract, it is because the JSContext// is in the process of shutting down. Since promise handlers are not// necessarily run while shutting down *anyway*, we simply ignore the error.// This is symmetric with the handling of errors in finishAsyncTaskCallback// which, since it is off the JSContext's owner thread, cannot report an// error anyway.if(!cx->runtime()->startAsyncTaskCallback(cx,task.get())){MOZ_ASSERT(!cx->isExceptionPending());returntrue;}// Per interface contract, after startAsyncTaskCallback succeeds,// finishAsyncTaskCallback *must* be called on all paths.AutoLockHelperThreadStatelock;if(!HelperThreadState().promiseTasks(lock).append(task.get())){Unused<<cx->runtime()->finishAsyncTaskCallback(task.get());ReportOutOfMemory(cx);returnfalse;}Unused<<task.release();HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER,lock);returntrue;}voidGlobalHelperThreadState::trace(JSTracer*trc){AutoLockHelperThreadStatelock;for(autobuilder:ionWorklist(lock))builder->trace(trc);for(autobuilder:ionFinishedList(lock))builder->trace(trc);if(HelperThreadState().threads){for(auto&helper:*HelperThreadState().threads){if(autobuilder=helper.ionBuilder())builder->trace(trc);}}for(ZoneGroupsItergroup(trc->runtime());!group.done();group.next()){jit::IonBuilder*builder=group->ionLazyLinkList().getFirst();while(builder){builder->trace(trc);builder=builder->getNext();}}for(autoparseTask:parseWorklist_)parseTask->trace(trc);for(autoparseTask:parseFinishedList_)parseTask->trace(trc);for(autoparseTask:parseWaitingOnGC_)parseTask->trace(trc);}voidHelperThread::handleGCHelperWorkload(AutoLockHelperThreadState&locked){MOZ_ASSERT(HelperThreadState().canStartGCHelperTask(locked));MOZ_ASSERT(idle());currentTask.emplace(HelperThreadState().gcHelperWorklist(locked).popCopy());GCHelperState*task=gcHelperTask();AutoSetContextRuntimeascr(task->runtime());{AutoUnlockHelperThreadStateunlock(locked);task->work();}currentTask.reset();HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER,locked);}voidJSContext::setHelperThread(HelperThread*thread){helperThread_=thread;}voidHelperThread::threadLoop(){MOZ_ASSERT(CanUseExtraThreads());JS::AutoSuppressGCAnalysisnogc;AutoLockHelperThreadStatelock;JSContextcx(nullptr,JS::ContextOptions());{AutoEnterOOMUnsafeRegionoomUnsafe;if(!cx.init(ContextKind::Background))oomUnsafe.crash("HelperThread cx.init()");}cx.setHelperThread(this);JS_SetNativeStackQuota(&cx,HELPER_STACK_QUOTA);while(true){MOZ_ASSERT(idle());// Block until a task is available. Save the value of whether we are// going to do an Ion compile, in case the value returned by the method// changes.boolionCompile=false;while(true){if(terminate)return;if((ionCompile=HelperThreadState().pendingIonCompileHasSufficientPriority(lock))||HelperThreadState().canStartWasmCompile(lock)||HelperThreadState().canStartPromiseTask(lock)||HelperThreadState().canStartParseTask(lock)||HelperThreadState().canStartCompressionTask(lock)||HelperThreadState().canStartGCHelperTask(lock)||HelperThreadState().canStartGCParallelTask(lock)||HelperThreadState().canStartIonFreeTask(lock)){break;}HelperThreadState().wait(lock,GlobalHelperThreadState::PRODUCER);}if(HelperThreadState().canStartGCParallelTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);handleGCParallelWorkload(lock);}elseif(HelperThreadState().canStartGCHelperTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);handleGCHelperWorkload(lock);}elseif(ionCompile){js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);handleIonWorkload(lock);}elseif(HelperThreadState().canStartWasmCompile(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM);handleWasmWorkload(lock);}elseif(HelperThreadState().canStartPromiseTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK);handlePromiseTaskWorkload(lock);}elseif(HelperThreadState().canStartParseTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);handleParseWorkload(lock);}elseif(HelperThreadState().canStartCompressionTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);handleCompressionWorkload(lock);}elseif(HelperThreadState().canStartIonFreeTask(lock)){js::oom::SetThreadType(js::oom::THREAD_TYPE_ION_FREE);handleIonFreeWorkload(lock);}else{MOZ_CRASH("No task to perform");}}}